package org.example;

import com.google.common.collect.Lists;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.example.autosuggest.*;
import org.example.listener.DiagnosticListener;
import org.example.project.Document;
import org.example.project.Project;
import org.example.semantic.SemanticTokensProvider;

import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class BallerinaTextDocumentService implements TextDocumentService {
    private BallerinaLanguageServer languageServer;
    private LSClientLogger clientLogger;

    private Project project = new Project();
    private LanguageClient client;

    public BallerinaTextDocumentService(BallerinaLanguageServer languageServer) {
        this.languageServer = languageServer;
        this.clientLogger = LSClientLogger.getInstance();
    }

    @Override
    public void didOpen(DidOpenTextDocumentParams didOpenTextDocumentParams) {
        TextDocumentItem textDocument = didOpenTextDocumentParams.getTextDocument();
        Document document = new Document(textDocument.getUri(), textDocument.getText(), textDocument.getLanguageId(), textDocument.getVersion());
        project.put(textDocument.getUri(), document);
        this.clientLogger.logMessage("Operation '" + "text/didOpen" +
                "' {fileUri: '" + textDocument.getUri() + "'} opened");
    }

    @Override
    public void didChange(DidChangeTextDocumentParams didChangeTextDocumentParams) {
        String uri = didChangeTextDocumentParams.getTextDocument().getUri();
        List<TextDocumentContentChangeEvent> changes = didChangeTextDocumentParams.getContentChanges();
        Document document = project.get(uri);
        if (null == document) {
            this.clientLogger.logMessage("Operation '" + "text/didChange" +
                    "' {fileUri: '" + uri + "'} failed, can not find document by the uri");
            return;
        }

        for (TextDocumentContentChangeEvent change : changes) {
            document.updateText(change.getText());
        }

        CharStream input = CharStreams.fromString(document.getText());
        com.neuronbit.parser.MySqlLexer lexer = new com.neuronbit.parser.MySqlLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        com.neuronbit.parser.MySqlParser parser = new com.neuronbit.parser.MySqlParser(tokens);
        parser.removeErrorListeners();
        DiagnosticListener listener = new DiagnosticListener();
        parser.addErrorListener(listener);

         parser.root();

        reportDiagnostics(listener.getDiagnostics(), uri);

        this.clientLogger.logMessage("Operation '" + "text/didChange" +
                "' {fileUri: '" + uri + "'} Changed");
    }

    @Override
    public void didClose(DidCloseTextDocumentParams didCloseTextDocumentParams) {
        String uri = didCloseTextDocumentParams.getTextDocument().getUri();
        project.remove(uri);
        this.clientLogger.logMessage("Operation '" + "text/didClose" +
                "' {fileUri: '" + uri + "'} Closed");
    }

    @Override
    public void didSave(DidSaveTextDocumentParams didSaveTextDocumentParams) {
        this.clientLogger.logMessage("Operation '" + "text/didSave" +
                "' {fileUri: '" + didSaveTextDocumentParams.getTextDocument().getUri() + "'} Saved");
    }

    @Override
    public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams position) {
        String uri = position.getTextDocument().getUri();
        int cursor = position.getPosition().getCharacter();
        int line = position.getPosition().getLine();
        String text = project.get(uri).getText();

        this.clientLogger.logMessage("79 Operation '" + "text/completion' {fileUri: '" + uri + "'} at " + line + "," + cursor + ", text=" + text);

        return CompletableFuture.supplyAsync(() -> {
            List<CompletionItem> result = new ArrayList<>();
            Collection<SuggestionItem> suggestions = null;
            try {
                this.clientLogger.logMessage("82 Operation '" + "text/completion' {fileUri: '" + uri + "'} at " + line + "," + cursor + ", text=" + text);
                LexerAndParserFactory lexerAndParserfactory = new ReflectionLexerAndParserFactory(
                        com.neuronbit.parser.MySqlLexer.class, com.neuronbit.parser.MySqlParser.class);
                this.clientLogger.logMessage("84 Operation '" + "text/completion' {fileUri: '" + uri + "'} at " + line + "," + cursor + ", text=" + text);

                final StringBuilder chompedString = new StringBuilder();
                BufferedReader reader = new BufferedReader(new StringReader(text));
                try {
                    int i = 0;
                    String lineStr = reader.readLine();
                    while (lineStr != null && i <= line) {
                        if (i < line) {
                            chompedString.append(lineStr);
                        } else {
                            chompedString.append(lineStr, 0, cursor);
                        }
                        i++;
                        lineStr = reader.readLine();
                    }
                } catch (Exception e) {
                    this.clientLogger.logMessage("105 Operation '" + "text/completion' {fileUri: '" + uri + "'} at "
                            + line + "," + cursor + ", failed" + e.getMessage());
                }

                String input = chompedString.toString().toUpperCase();
                if (StringUtils.isNoneBlank(input)) {
                    AutoSuggester suggester = new AutoSuggester(lexerAndParserfactory, input);
                    this.clientLogger.logMessage("90 Operation '" + "text/completion' {fileUri: '" + uri + "'} at " + line + "," + cursor + ", input='" + input + "'" + ", text='" + text + "'");
                    suggester.setCasePreference(CasePreference.UPPER);
                    this.clientLogger.logMessage("92 Operation '" + "text/completion' {fileUri: '" + uri + "'} at " + line + "," + cursor + ", input=" + input);
                    suggestions = suggester.suggestCompletions();
                    this.clientLogger.logMessage("94 Operation '" + "text/completion' {fileUri: '" + uri + "'} at " + line + "," + cursor + ", input=" + input + ", suggestions" + suggestions);
                    if (null != suggestions && !suggestions.isEmpty()) {
                        for (SuggestionItem suggestion : suggestions) {
                            CompletionItem completionItem = new CompletionItem();
                            completionItem.setLabel(suggestion.getToken());
                            completionItem.setInsertText(suggestion.getToken());
                            completionItem.setDetail(suggestion.getToken());
                            completionItem.setKind(CompletionItemKind.Snippet);
                            result.add(completionItem);
                        }
                    }
                }
//                this.clientLogger.logMessage("105 Operation '" + "text/completion' {fileUri: '" + uri + "'} at " + line + "," + cursor + ", input=" + input);
            } catch (Exception e) {
                this.clientLogger.logMessage("130 Operation '" + "text/completion' {fileUri: '" + uri + "'} at "
                        + line + "," + cursor + ", failed" + e.getMessage());
                CompletionItem completionItem = new CompletionItem();
                completionItem.setLabel("Test completion item");
                completionItem.setInsertText("Test");
                completionItem.setDetail("Snippet");
                completionItem.setKind(CompletionItemKind.Snippet);
                result.add(completionItem);
            }
//            this.clientLogger.logMessage("116 Operation '" + "text/completion' {fileUri: '" + uri + "'} at " + line + "," + cursor + ", result=" + result.size());
            return Either.forLeft(result);
        });
    }

    @Override
    public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
        SemanticTokens semanticTokens = SemanticTokensProvider.semanticTokensFull(project, params);
        this.clientLogger.logMessage("textDocument/semanticTokensFull: " + params.getTextDocument().getUri());
        return CompletableFuture.completedFuture(semanticTokens);
    }

    public void reportDiagnostics(final List<Diagnostic> diagnostics, final String documentUri) {
        PublishDiagnosticsParams result = new PublishDiagnosticsParams();
        result.setUri(documentUri);

        if (diagnostics != null) {
            for (var d : diagnostics) {
                assert d.getRange() != null;
            }

            result.setDiagnostics(diagnostics);
        } else {
            result.setDiagnostics(new ArrayList<>());
        }

        this.client.publishDiagnostics(result);
    }

    public void connect(LanguageClient client) {
        this.client = client;
    }
}
